﻿Imports System.ComponentModel.Composition.Hosting
Imports System.ComponentModel.Composition
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

''' <summary>
''' 信息发送插件集合的包装
''' </summary>
''' <remarks></remarks>
Public Class InfoSenders
    Implements IDisposable

    Private _container As CompositionContainer

    ''' <summary>
    ''' 信息发送插件列表
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <ImportMany()>
    Protected Property InfoSender As IEnumerable(Of Lazy(Of IInfoSender, IExtensionCompentMetadata))

    ''' <summary>
    ''' 初始化一个信息发送插件集合，将在程序执行目录查找插件
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        Me.New(".")
    End Sub

    ''' <summary>
    ''' 初始化一个信息发送插件集合，将在指定目录查找插件
    ''' </summary>
    ''' <param name="path">要进行插件查找的目录</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal path As String)
        If path Is Nothing Then path = "."
        Init({path})
    End Sub

    ''' <summary>
    ''' 初始化一个信息发送插件集合，将在指定的数个目录查找插件
    ''' </summary>
    ''' <param name="paths">指定的目录集合</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal paths As IEnumerable(Of String))
        If paths Is Nothing Then
            paths = {"."}
        End If
        Init(paths)
    End Sub

    Private Sub Init(ByVal paths As IEnumerable(Of String))
        Dim catalog = New AggregateCatalog()
        'catalog.Catalogs.Add(New AssemblyCatalog(GetType(InfoSenders).Assembly))
        For Each path In paths
            If Not String.IsNullOrEmpty(path) Then
                If IO.Directory.Exists(path) Then
                    catalog.Catalogs.Add(New DirectoryCatalog(path))
                End If
            End If
        Next
        _container = New CompositionContainer(catalog)
        Try
            _container.ComposeParts(Me)
            If InfoSender IsNot Nothing Then
                For Each ISender In InfoSender
                    ISender.Value.StartUp()
                    If Not SendersState.ContainsKey(ISender.Metadata.Name) Then
                        SendersState.Add(ISender.Metadata.Name, Tuple.Create(False, Integer.MaxValue))
                    End If
                Next
            End If
        Catch ex As Exception
            Debug.WriteLine(ex.ToString)
            Throw
        End Try
    End Sub

    Private _Names As String()
    ''' <summary>
    ''' 获取可用的发送器名
    ''' </summary>
    ''' <value>可用的发送器名</value>
    ''' <returns>可用的发送器名</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Names As IEnumerable(Of String)
        Get
            If _Names Is Nothing Then
                _Names = (From s In InfoSender Select s.Metadata.Name).ToArray
            End If
            Return _Names
        End Get
    End Property

    <Obsolete("使用SenderState替代")>
    Private _EnableSenders As Dictionary(Of String, Boolean) = New Dictionary(Of String, Boolean)
    ''' <summary>
    ''' 指示指定插件是否允许工作的集合
    ''' </summary>
    ''' <value>指示指定插件是否允许工作的集合</value>
    ''' <returns>指示指定插件是否允许工作的集合</returns>
    ''' <remarks></remarks>
    <Obsolete("使用SenderState替代")>
    Public ReadOnly Property EnableSenders As Dictionary(Of String, Boolean)
        Get
            Return _EnableSenders
        End Get
    End Property

    Private _SendersState As Dictionary(Of String, Tuple(Of Boolean, Integer))
    ''' <summary>
    ''' 指示发送插件是否运行及优先级
    ''' </summary>
    ''' <value>指示发送插件是否运行及优先级</value>
    ''' <returns>指示发送插件是否运行及优先级</returns>
    ''' <remarks></remarks>
    Public Property SendersState As Dictionary(Of String, Tuple(Of Boolean, Integer))
        Get
            If _SendersState Is Nothing Then _SendersState = New Dictionary(Of String, Tuple(Of Boolean, Integer))
            Return _SendersState
        End Get
        Set(value As Dictionary(Of String, Tuple(Of Boolean, Integer)))
            _SendersState = value
        End Set
    End Property

    ''' <summary>
    ''' 使用所有可用且启用的插件发送信息
    ''' </summary>
    ''' <param name="message">要发送的信息</param>
    ''' <returns>一个二元组，指示是否完全成功以及错误列表</returns>
    ''' <remarks></remarks>
    Public Function Update(ByVal message As String) As Tuple(Of Boolean, IEnumerable(Of Exception))
        Dim ais = From s In InfoSender.AsParallel Where SendersState(s.Metadata.Name).Item1 AndAlso s.Value.GetAvailability Select s.Value

        Dim e As Exception = If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing)
        ais.ForAll(Sub(ai)
                       Try
                           ai.SendInfo(message)
                       Catch ex As Exception
                           _ExceptionStack.Push(ex)
                       End Try
                   End Sub)

        If If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing) Is e Then
            Return Tuple.Create(Of Boolean, IEnumerable(Of Exception))(True, Nothing)
        Else
            Dim EL As New List(Of Exception)
            Do Until If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing) Is e
                EL.Add(If(ExceptionStack.Count <> 0, ExceptionStack.Pop, Nothing))
            Loop
            Return Tuple.Create(Of Boolean, IEnumerable(Of Exception))(True, EL)
        End If
    End Function

    ''' <summary>
    ''' 使用指定的插件发送消息，无论是否启用
    ''' </summary>
    ''' <param name="message">要发送的消息</param>
    ''' <param name="Name">要使用的插件名</param>
    ''' <returns>一个二元组，指示是否完全成功以及错误列表</returns>
    ''' <remarks></remarks>
    Public Function UpdateOne(ByVal message As String, ByVal Name As String) As Tuple(Of Boolean, IEnumerable(Of Exception))
        Dim s = From se In InfoSender.AsParallel Where se.Metadata.Name = Name Select se.Value

        Dim e As Exception = If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing)
        s.ForAll(Sub(sd)
                     Try
                         sd.SendInfo(message)
                     Catch ex As Exception
                         _ExceptionStack.Push(ex)
                     End Try
                 End Sub)

        If If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing) Is e Then
            Return Tuple.Create(Of Boolean, IEnumerable(Of Exception))(True, Nothing)
        Else
            Dim EL As New List(Of Exception)
            Do Until If(ExceptionStack.Count <> 0, ExceptionStack.Peek, Nothing) Is e
                EL.Add(If(ExceptionStack.Count <> 0, ExceptionStack.Pop, Nothing))
            Loop
            Return Tuple.Create(Of Boolean, IEnumerable(Of Exception))(True, EL)
        End If

    End Function

    Private _ExceptionStack As New ConcurrentStack(Of Exception)
    ''' <summary>
    ''' 获得错误堆栈
    ''' </summary>
    ''' <value>错误堆栈</value>
    ''' <returns>错误堆栈</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ExceptionStack As Stack(Of Exception)
        Get
            Return New Stack(Of Exception)(_ExceptionStack.ToArray)
        End Get
    End Property

    ''' <summary>
    ''' 显示指定的插件设置窗口
    ''' </summary>
    ''' <param name="Name">指定的插件名</param>
    ''' <remarks></remarks>
    Public Sub ShowSetting(ByVal Name As String)
        Dim s = (From se In InfoSender Where se.Metadata.Name = Name Select se.Value).ToArray()
        If s.Count = 0 Then Return
        s(0).ShowSetting()
    End Sub

    Public Function GetAvailabilities() As Dictionary(Of String, Boolean)
        Dim p = From s In InfoSender Select key = s.Metadata.Name, value = s.Value.GetAvailability

        Dim d As New Dictionary(Of String, Boolean)
        For Each s In p
            d.Add(s.key, s.value)
        Next
        Return d
    End Function

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 检测冗余的调用

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: 释放托管状态(托管对象)。
                For Each infos In InfoSender
                    infos.Value.ShutDown()
                Next
                _container.Dispose()
                _Names = Nothing
                _ExceptionStack = Nothing
                _EnableSenders = Nothing
            End If

            ' TODO: 释放非托管资源(非托管对象)并重写下面的 Finalize()。
            ' TODO: 将大型字段设置为 null。
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: 仅当上面的 Dispose(ByVal disposing As Boolean)具有释放非托管资源的代码时重写 Finalize()。
    'Protected Overrides Sub Finalize()
    '    ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Visual Basic 添加此代码是为了正确实现可处置模式。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
